use crate::llvm::{LLVMProgram, InstrType};
use crate::sym_type::*;
use crate::config::*;
use super::scopes::{Labels};
use crate::float::{format_double, parse_float};

/// 检查数组/指针是同一类型<br>
/// 对于数组/指针为const的情况，额外加入一层指针<br>
/// 当一层出现指针时，这一层恒定为相同类型
pub fn arr_is_eq(arr1: &SymType, arr2: &SymType) -> bool {
    if let (Width::Arr{tar: tar1, dims: dims1}, Width::Arr{tar: tar2, dims: dims2}) = (&arr1.width, &arr2.width) {
        let mut dims1 = dims1.clone();
        if arr1.is_const {
            dims1.insert(0, -1);
        }
        let mut dims2 = dims2.clone();
        if arr2.is_const {
            dims2.insert(0, -1);
        }

        if tar1.width != tar2.width {
            return false;
        }
        if dims1.len() != dims2.len() {
            return false;
        }
        for cnt in 0..dims1.len() {
            if dims1[cnt] != dims2[cnt] && dims1[cnt] != -1 && dims2[cnt] != -1 {
                return false;
            }
        }
        return true;
    } else {
        panic!("Should not appear");
    }
}

/// 检查ty是否为数组/指针
fn is_arr(ty: &SymType) -> bool {
    if let Width::Arr{tar: _, dims: _} = ty.width {
        true
    } else {
        false
    }
}

/// 进行常量和变量的类型转换
pub fn type_conver(program: &mut LLVMProgram, labels: &mut Labels, value: String, ty: &SymType, ty2: &SymType) -> String {
    if ty.width == ty2.width {
        return value;
    }
    if let (Width::Arr{tar: tar1, dims: dims1}, Width::Arr{tar: tar2, dims: dims2}) = (&ty.width, &ty2.width) {
        if arr_is_eq(ty, ty2) {
            return value;
        }

        let mut dims1 = dims1.clone();
        if ty.is_const {
            dims1.insert(0, -1);
        }
        let mut dims2 = dims2.clone();
        if ty2.is_const {
            dims2.insert(0, -1);
        }
        dims1.remove(0);
        dims2.remove(0);
        let new_ty = SymType::new(Width::Arr{tar: tar1.clone(), dims: dims1}, false);
        let new_ty2 = SymType::new(Width::Arr{tar: tar2.clone(), dims: dims2}, false);

        let cast_res = labels.pop_num_str();
        let ty_vec = vec!(&new_ty, &new_ty2);
        let str_vec = vec!(cast_res.as_str(), value.as_str());
        program.push_instr(InstrType::BitCast, str_vec, ty_vec);
        vprintln!("Warning: cast {} from {:?} to {:?}", value, ty, ty2);

        return cast_res;
    }

    let is_var = ty.is_const == false;

    if is_var {
        if ty.width > ty2.width {
            let mut result = value;
            if is_arr(ty) {
                panic!("Should not appear");
            } else if ty.width == Width::Float {
                let label = labels.pop_num_str();
                program.push_instr(
                    InstrType::Fptosi, 
                    vec!(&label, &result),
                    vec!(&ty, &ty2),
                );
                result = label;
            } else if ty2.width == Width::I1 {
                let ty_vec = vec!(ty);
                let label = labels.pop_num_str();
                let str_vec = vec!("ne", label.as_str(), "0", result.as_str());
                program.push_instr(InstrType::Icmp, str_vec, ty_vec);
                result = label;
            }
            result
        } else {
            let mut result = value;
            if is_arr(ty2) {
                panic!("Should not appear");
            } else if ty2.width == Width::Float {
                let label = labels.pop_num_str();
                program.push_instr(
                    InstrType::Sitofp, 
                    vec!(&label, &result),
                    vec!(&ty, &ty2),
                );
                result = label;
            } else {
                let label = labels.pop_num_str();
                program.push_instr(
                    InstrType::Zext, 
                    vec!(&label, &result),
                    vec!(&ty, &ty2),
                );
                result = label;
            } // width match
            result
        } // width compare
    } else { // is_var else
        if ty.width > ty2.width {
            if is_arr(ty) {
                panic!("Should not appear");
            } else if ty.width == Width::Float {
                let num: f32 = parse_float(value.as_str());
                let int_num: i32 = num as i32;
                int_num.to_string()
            } else if ty2.width == Width::I1 {
                let num: i32 = value.parse().unwrap();
                let bool_num = (num != 0) as i32;
                bool_num.to_string()
            } else {
                value
            }
        } else {
            if is_arr(ty2) {
                panic!("Should not appear");
            } else if ty2.width == Width::Float {
                let result: i32 = value.parse().unwrap();
                format_double(parse_float(result.to_string().as_str()))
            } else {
                value
            }
        }
    }
}

/// 比较两个数值类型，若类型不同，调用type_conver将类型低转换为高的，最后返回比较结果
pub fn type_cmp(program: &mut LLVMProgram, labels: &mut Labels, ty1: SymType, op1: String, ty2: SymType, op2: String) -> (SymType, String, String) {
    if ty1.width == ty2.width {
        return (ty1, op1, op2);
    }

    if ty1.width > ty2.width {
        let op2 = type_conver(program, labels, op2, &ty2, &ty1);
        (ty1, op1, op2)
    } else {
        let op1 = type_conver(program, labels, op1, &ty1, &ty2);
        (ty2, op1, op2)
    }
}

